package cc.noau.coder.generator;

import cc.noau.coder.db.DBConnector;
import cc.noau.coder.bean.Table;
import cc.noau.coder.bean.CoderSetting;
import cc.noau.coder.bean.SettingRow;

import java.io.*;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

/**
 * <h1> 代码生成器 </h1>
 * <p>
 * @author lxz
 */
public class GenericCodeGenerator extends AbstractCodeGenerator {

    private CoderSetting setting;

    public GenericCodeGenerator(CoderSetting coderSetting) {
        setting = coderSetting;
    }

    /**
     * 生成代码
     * 流程如下：
     * 1、提取配置中的代码模板
     * 2、遍历配置中的映射规则列表
     * 3、按照每一条映射规则，计算包路径，代码文件输出路径，文件名等
     * 4、按照模板，参数，生成实际的代码。
     * 5、输出代码到文件中
     * 6、刷新目录
     *
     * @throws Exception 各种可能的异常，外层会 catch 住，然后弹提示框告知用户
     */
    public void generate() throws Exception {

        // 映射规则配置行
        List<SettingRow> settingRows = setting.getSettingRows();

        for (SettingRow settingRow : settingRows) {
            // java 实体包名
            String javaModelPackage = toRealPackageName(setting.getJavaModelPackage(), settingRow.getModule());
            // java 实体VO包名
            String javaModelVoPackage = toRealPackageName(setting.getJavaModelVoPackage(), settingRow.getModule());
            // java 映射接口包名
            String javaMapperPackage = toRealPackageName(setting.getJavaInterfacePackage(), settingRow.getModule());
            // XML 映射文件包名
            String mapperXmlPackage = toRealPackageName(setting.getMapperXMLPackage(), settingRow.getModule());
            // java Service包名
            String javaServicePackage = toRealPackageName(setting.getJavaServicePackage(), settingRow.getModule());
            // java Service实现包名
            String javaServiceImplPackage = toRealPackageName(setting.getJavaServiceImplPackage(), settingRow.getModule());
            // java Controller包名
            String javaControllerPackage = toRealPackageName(setting.getJavaControllerPackage(), settingRow.getModule());

            // java 实体类名
            String javaModelFileName = settingRow.getDomainObjectName() + ".java";
            // java 实体查询类名
            String javaQueryModelFileName = "Query" + settingRow.getDomainObjectName() + "VO.java";
            // java 实体查询条件类名
            String javaModelConditionFileName = settingRow.getDomainObjectName() + "Condition.java";
            // java 映射接口名
            String javaMapperFileName = settingRow.getDomainObjectName() + "Mapper.java";
            // XML 映射文件名
            String javaMapperXmlFileName = settingRow.getDomainObjectName() + "Mapper.xml";
            // java Service类名
            String javaServiceFileName = settingRow.getDomainObjectName() + "Service.java";
            // java Impl实现类名
            String javaServiceImplFileName = settingRow.getDomainObjectName() + "ServiceImpl.java";
            // java Controller类名
            String javaControllerFileName = settingRow.getDomainObjectName() + "Controller.java";


            // java 实体类输出路径
            String javaModelDirPath = toDirPath(settingRow.getJavaModelPath(), javaModelPackage, false);
            // java 实体查询类输出路径
            String javaQueryModelDirPath = toDirPath(settingRow.getJavaModelPath(), javaModelVoPackage + ".query", false);
            // java 实体查询条件类输出路径
            String javaModelConditionDirPath = toDirPath(settingRow.getJavaModelPath(), javaModelVoPackage + ".condition", false);
            // java 映射接口输出路径
            String javaMapperDirPath = toDirPath(settingRow.getMapperPath(), javaMapperPackage, false);
            // XML 映射文件输出路径，对于XML文件，如果存在resource目录，则会输出到resources目录下，否则和接口同一个源码路径
            String mapperXmlDirPath = toDirPath(settingRow.getMapperPath(), mapperXmlPackage, true);
            // java Service类输出路径
            String javaServiceDirPath = toDirPath(settingRow.getJavaServicePath(), javaServicePackage, false);
            // java Service实现类输出路径
            String javaServiceImplDirPath = toDirPath(settingRow.getJavaImplPath(), javaServiceImplPackage, false);
            // java Controller类输出路径
            String javaControllerDirPath = toDirPath(settingRow.getJavaControllerPath(), javaControllerPackage, false);


            // 数据库表的元数据
            Table table = DBConnector.getTableDescription(setting, settingRow.getTableName());

            String generateFileType = setting.getGenerateFileType();

            if(generateFileType.contains("model")){
                // 实体类代码模板
                String javaModelTemplate = URLDecoder.decode(readDefaultTemplate(setting.getJavaModelTemplate()), "UTF-8");
                // 读取需要留存的内容
                StringBuilder javaMapperRetainedCode = readRetainedContent(javaModelDirPath, javaModelFileName);
                // 实体类 源代码写入文件
                writeToFile(javaModelDirPath, javaModelFileName,
                        replace(javaModelTemplate, generateParamMap(setting, settingRow, table, javaMapperRetainedCode)));
            }

            if(generateFileType.contains("query")) {
                // 实体查询类代码模板
                String javaQueryModelTemplate = URLDecoder.decode(readDefaultTemplate(setting.getJavaQueryModelTemplate()), "UTF-8");
                // 读取需要留存的内容
                StringBuilder javaQueryMapperRetainedCode = readRetainedContent(javaModelDirPath, javaQueryModelFileName);
                // 实体查询类 源代码写入文件
                writeToFile(javaQueryModelDirPath, javaQueryModelFileName,
                        replace(javaQueryModelTemplate, generateParamMap(setting, settingRow, table, javaQueryMapperRetainedCode)));
            }

            if(generateFileType.contains("condition")) {
                // 实体查询条件类代码模板
                String javaModelConditionTTemplate = URLDecoder.decode(readDefaultTemplate(setting.getJavaModelConditionTemplate()), "UTF-8");
                // 读取需要留存的内容
                StringBuilder javaConditionMapperRetainedCode = readRetainedContent(javaModelDirPath, javaModelConditionFileName);
                // 实体查询条件类 源代码写入文件
                writeToFile(javaModelConditionDirPath, javaModelConditionFileName,
                        replace(javaModelConditionTTemplate, generateParamMap(setting, settingRow, table, javaConditionMapperRetainedCode)));
            }

            if(generateFileType.contains("mapper")) {
                // 映射接口代码模板
                String javaMapperTemplate = URLDecoder.decode(readDefaultTemplate(setting.getJavaInterfaceTemplate()), "UTF-8");
                // 读取需要留存的内容
                StringBuilder avaMapperRetainedCode = readRetainedContent(javaMapperDirPath, javaMapperFileName);
                // 映射接口 写入文件
                writeToFile(javaMapperDirPath, javaMapperFileName,
                        replace(javaMapperTemplate, generateParamMap(setting, settingRow, table, avaMapperRetainedCode)));
            }

            if(generateFileType.contains("xml")) {
                // XML映射文件代码模板
                String mapperXmlTemplate = URLDecoder.decode(readDefaultTemplate(setting.getMapperXmlTemplate()), "UTF-8");
                // 读取需要留存的内容
                StringBuilder mapperXmlRetainedCode = readRetainedContent(mapperXmlDirPath, javaMapperXmlFileName);
                // XML 写入文件
                writeToFile(mapperXmlDirPath, javaMapperXmlFileName,
                        replace(mapperXmlTemplate, generateParamMap(setting, settingRow, table, mapperXmlRetainedCode)));
            }

            if(generateFileType.contains("service")) {
                // Service类代码模板
                String javaServiceTemplate = URLDecoder.decode(readDefaultTemplate(setting.getJavaServiceTemplate()), "UTF-8");
                // 读取需要留存的内容
                StringBuilder javaServiceRetainedCode = readRetainedContent(javaServiceDirPath, javaServiceFileName);
                // Service类 源代码写入文件
                writeToFile(javaServiceDirPath, javaServiceFileName,
                        replace(javaServiceTemplate, generateParamMap(setting, settingRow, table, javaServiceRetainedCode)));
            }

            if(generateFileType.contains("impl")) {
                // Impl实现类代码模板
                String javaServiceImplTemplate = URLDecoder.decode(readDefaultTemplate(setting.getJavaServiceImplTemplate()), "UTF-8");
                // 读取需要留存的内容
                StringBuilder javaServiceImplRetainedCode = readRetainedContent(javaServiceImplDirPath, javaServiceImplFileName);
                // Service实现类 源代码写入文件
                writeToFile(javaServiceImplDirPath, javaServiceImplFileName,
                        replace(javaServiceImplTemplate, generateParamMap(setting, settingRow, table, javaServiceImplRetainedCode)));
            }

            if(generateFileType.contains("controller")) {
                // 控制层代码模板
                String javaControllerTemplate = URLDecoder.decode(readDefaultTemplate(setting.getJavaControllerTemplate()), "UTF-8");
                // 读取需要留存的内容
                StringBuilder javaControllerRetainedCode = readRetainedContent(javaControllerDirPath, javaControllerFileName);
                // Controller类 源代码写入文件
                writeToFile(javaControllerDirPath, javaControllerFileName,
                        replace(javaControllerTemplate, generateParamMap(setting, settingRow, table, javaControllerRetainedCode)));
            }

        }
    }

    /**
     * 拼装代码文件输出路径
     *
     * @param configPath      配置中的路径
     * @param packageName     包名
     * @param useResourcesDir 是否主动切换到resources。对于XML文件来说，优先输出到resources下。
     * @return 代码文件输出路径
     */
    private String toDirPath(String configPath, String packageName, boolean useResourcesDir) {
        String dirPathArry[] = configPath.split("\\|");
        String dirPath = dirPathArry[0];
        Path path = Paths.get(dirPath);
        if (useResourcesDir) {
            File file = new File(path.getParent().toAbsolutePath() + "/resources");
            if (file.exists()) {
                dirPath = file.getAbsolutePath(); // 源码路径切换到 resources 目录
            }
        }
        return dirPath + "/" + packageName.replaceAll("\\.", "/");
    }

    /**
     * 从模板中获取文件名
     * 这个步骤似乎显得复杂，多余。实际上是考虑到用户对于生成的代码文件名存在
     * 命名规则的需求。比如实体名是否需要以 BO，或者PO为后缀。接口是否以I起始。
     * 考虑到命名风格的不同。让用户可以在模板
     *
     * @param template 模板
     * @return 文件名
     * @deprecated 这个实现起来似乎有很多问题，考虑换一种做法
     */
    @Deprecated
    private String getFileNameFromTemplate(String template, String fileType) {
        StringBuilder fileName = new StringBuilder();
        boolean start = false;
        String startStr = " " + fileType + " ";
        for (int i = template.indexOf(startStr) + startStr.length(); i < template.length(); i++) {
            if (template.charAt(i) != ' ') {
                start = true;
                fileName.append(template.charAt(i));
            } else if (start) {
                break;
            }
        }
        fileName.append(".java");
        return fileName.toString();
    }

    /**
     * 读取模板
     *
     * @param fileName
     * @return
     */
    public String readDefaultTemplate(String fileName) {
        StringBuilder builder = new StringBuilder("");
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    new BufferedInputStream(getClass().getClassLoader().getResourceAsStream(this.setting.getTemplatePath()+"/" + fileName)), "UTF-8"));
            String line;
            while ((line = reader.readLine()) != null) {
                builder.append(line).append("\n");
            }
            builder.deleteCharAt(builder.length() - 1);
            return URLEncoder.encode(builder.toString(), "UTF-8");
        } catch (Exception e) {
            System.err.println("资源目录下找不到模板文件: "+ this.setting.getTemplatePath()+"/" +fileName);
            return "找不到模板文件 "+ fileName;
        }
    }

}
